<?php
/**
 * ArrayUtil 클래스
 *
 * @package php.lang
 */

/**
 * 배열을 위한 유틸리티 Wrapper 클래스 
 *
 * <code>
 * 
 * // 1. ArrayUtil 생성 
 *  new ArrayUtil(array(1,2,3,4,5));    or    A_(array(1,2,3,4,5));
 *
 * // 2. 참조형태 함수 쓰기
 * $arr = A_(array(5,4,3,2,1));
 * print_r($arr->sort()->to);
 *
 * // 3. 리턴형태 함수 쓰기 
 * $arr = A_(array('a', 'b', 'c', 'd', 'e'));
 * print_r($arr->map('strtoupper')->return);
 * 
 * // 4. 리턴형태 함수를 참조형태로 쓰기 
 * $arr = A_(array('a', 'b', 'c', 'd', 'e'));
 * print_r($arr->r(map, 'strtoupper')->to);
 * or
 * print_r($arr->ref()->map('strtoupper')->to);
 *
 * // 5. 리턴값을 ArrayUtil 객체로 전환해서 쓰기 
 * // object 속성은 return 값으로 받은 배열을 ArrayUtil 로 변환해준다. 
 * $arr = A_(array('a', 'b', 'c', 'd', 'e'));
 * print($arr->r(map, 'strtoupper')->object->r(map, 'strtolower')->return);
 *
 * // 6. 비어있는 ArrayUtil 객체 채우기 
 * $arr = A_()->r(range, 1, 10)->sum();
 * print_r($arr->return); 
 *
 * </code>
 *
 * <pre>
 * - 배열관련 함수 목록 
 * 1. 참조형태 
 * 2. 리턴형태
 * </pre>
 * 
 * @package php.lang 
 */
class ArrayUtil extends ArrayObject implements Iterator {
	private $source;	
	private $current;
	private $history;

	private $index;
	private $return;
	private $is_ref = false;

	/** 
	 * 매개변수로 적용 안함 
	 */
	const POS_ZERO	= -1;			

	/** 
	 * 첫번째 매개변수 
	 */
	const POS_FIRST = 0;			

	/** 
	 * 두번째 매개변수 
	 */
	const POS_SECOND = 1;			

	/** 
	 * 마지막 매개변수 
	 */
	const POS_MAX	= 1000;			

	// 기본 함수 테이블 
	private $function_list = array();

	/**
	 * 생성자 
	 *
	 */
	public function __construct($arr = array()) { 
		$this->source	= $arr;
		$this->current	= $arr;
		$this->index	= 0;

		$this->init();
	}

	public function init() { 
		
		// 외부 메소드 호출 
		$this->setFunctionList('map',				'array_map',			self::POS_SECOND,	false);
		$this->setFunctionList('change_key_case',	'array_change_key_case',self::POS_FIRST,	false);
		$this->setFunctionList('filter',			'array_filter',			self::POS_FIRST,	false);
		$this->setFunctionList('count_values',		'array_count_values',	self::POS_FIRST,	false);
		$this->setFunctionList('chunk',				'array_chunk',			self::POS_FIRST,	false);
		$this->setFunctionList('combine',			'array_combine',		self::POS_FIRST,	false);
		$this->setFunctionList('diff_assoc',		'array_diff_assoc',		self::POS_FIRST,	false);
		$this->setFunctionList('diff_uassoc',		'array_diff_uassoc',	self::POS_FIRST,	false);
		$this->setFunctionList('diff_ukey',			'array_diff_ukey',		self::POS_FIRST,	false);
		$this->setFunctionList('diff_key',			'array_diff_key',		self::POS_FIRST,	false);
		$this->setFunctionList('diff',				'array_diff',			self::POS_FIRST,	false);
		$this->setFunctionList('fill_keys',			'array_fill_keys',		self::POS_FIRST,	false);
		$this->setFunctionList('fill',				'array_fill',			self::POS_ZERO,		false);
		$this->setFunctionList('flip',				'array_flip',			self::POS_FIRST,	false);
		$this->setFunctionList('intersect_assoc',	'array_intersect_assoc',self::POS_FIRST,	false);
		$this->setFunctionList('intersect_key',		'array_intersect_key',	self::POS_FIRST,	false);
		$this->setFunctionList('intersect_uassoc',	'array_intersect_uassoc',self::POS_FIRST,	false);
		$this->setFunctionList('intersect_ukey',	'array_intersect_ukey',	self::POS_FIRST,	false);
		$this->setFunctionList('intersect',			'array_intersect',		self::POS_FIRST,	false);
		$this->setFunctionList('keys',				'array_keys',			self::POS_FIRST,	false);
		$this->setFunctionList('key_exists',		'array_key_exists',		self::POS_SECOND,	false);
		$this->setFunctionList('merge_recursive',	'array_merge_recursive',self::POS_FIRST,	false);
		$this->setFunctionList('merge',				'array_merge',			self::POS_FIRST,	false);
		$this->setFunctionList('multisort',			'array_multisort',		self::POS_MAX,		true);
		$this->setFunctionList('pad',				'array_pad',			self::POS_FIRST,	false);
		$this->setFunctionList('pop',				'array_pop',			self::POS_FIRST,	true);
		$this->setFunctionList('product',			'array_product',		self::POS_FIRST,	false);
		$this->setFunctionList('push',				'array_push',			self::POS_FIRST,	true);
		$this->setFunctionList('rand',				'array_rand',			self::POS_FIRST,	false);
		$this->setFunctionList('reduce',			'array_reduce',			self::POS_FIRST,	false);
		$this->setFunctionList('reverse',			'array_reverse',		self::POS_FIRST,	false);
		$this->setFunctionList('search',			'array_search',			self::POS_SECOND,	false);
		$this->setFunctionList('shift',				'array_shift',			self::POS_FIRST,	true);
		$this->setFunctionList('slice',				'array_slice',			self::POS_FIRST,	false);
		$this->setFunctionList('splice',			'array_splice',			self::POS_FIRST,	true);
		$this->setFunctionList('sum',				'array_sum',			self::POS_FIRST,	false);
		$this->setFunctionList('unique',			'array_unique',			self::POS_FIRST,	false);
		$this->setFunctionList('unshift',			'array_unshift',		self::POS_FIRST,	true);
		$this->setFunctionList('values',			'array_values',			self::POS_FIRST,	false);
		$this->setFunctionList('walk_recursive',	'array_walk_recursive',	self::POS_FIRST,	true);
		$this->setFunctionList('walk',				'array_walk',			self::POS_FIRST,	true);
		$this->setFunctionList('arsort',			'arsort',				self::POS_FIRST,	true);
		$this->setFunctionList('asort',				'asort',				self::POS_FIRST,	true);
		$this->setFunctionList('compact',			'compact',				self::POS_ZERO,		false);
		$this->setFunctionList('each',				'each',					self::POS_FIRST,	true);
		$this->setFunctionList('extract',			'extract',				self::POS_FIRST,	false);
		$this->setFunctionList('in',				'in_array',				self::POS_FIRST,	false);
		$this->setFunctionList('krsort',			'krsort',				self::POS_FIRST,	true);
		$this->setFunctionList('ksort',				'ksort',				self::POS_FIRST,	true);
		$this->setFunctionList('natcasesort',		'natcasesort',			self::POS_FIRST,	true);
		$this->setFunctionList('natsort',			'natsort',				self::POS_FIRST,	true);
		$this->setFunctionList('range',				'range',				self::POS_ZERO,		false);
		$this->setFunctionList('rsort',				'rsort',				self::POS_FIRST,	true);
		$this->setFunctionList('shuffle',			'shuffle',				self::POS_FIRST,	true);
		$this->setFunctionList('sort',				'sort',					self::POS_FIRST,	true);
		$this->setFunctionList('uasort',			'uasort',				self::POS_FIRST,	true);
		$this->setFunctionList('uksort',			'uksort',				self::POS_FIRST,	true);
		$this->setFunctionList('usort',				'usort',				self::POS_FIRST,	true);
		$this->setFunctionList('implode',			'implode',				self::POS_FIRST,	false);
		$this->setFunctionList('explode',			'explode',				self::POS_ZERO,		false);

		// 내부 메소드 호출 
		$this->setFunctionList('loop',				array($this, '_loop'),	self::POS_FIRST,	false);
	}

	public function setFunctionList($name, $run, $pos, $is_reference) { 
		$this->function_list[$name] = array('function' => $run,	'pos' => $pos, 'is_reference' => $is_reference);
	}

	/**
	 * array 관련함수 자동실행 
	 *
	 * @return ArrayUtil 
	 */
	public function __call($method, $args) { 

		$method = strtolower($method);

		if (! array_key_exists($method, $this->function_list)) return  ;

		// ArrayUtil 객체가 있으면 array로 변환시킴 
		foreach ($args as $key => $value) { 
			$args[$key] = is_a($value, "ArrayUtil") ? $value->to : $value;
		}

		// 자기자신이 포함이 되면 매개변수 재작성 
		if ($this->getPos($method) > self::POS_ZERO) { 
			$this->createMethodArgs($method, $args);	
		}

		// 메소드 실행 
		$this->return = call_user_func_array($this->getFunction($method), $args);

		// 참조 형태이고 is_reference 속성이 false 이면 
		// 즉, 현재 return 값을 current 로 바꿈 
		if ($this->is_ref && $this->hasRef($method) == false) { 
			$this->current = $this->return;
		}

		// 변경 로그 남기기 
		$this->log($method, $args, $this->current);

		// 참조 초기화 
		$this->ref(false);

		return $this;
	}

	/*
	 * ArrayObject 에 정의되어 있는 정렬메소드 재정의 
 	 */

	/**
	 * asort
	 *
	 * @return ArrayUtil
	 */
	public function asort ( ) { 
		$arr = func_get_args();
		return $this->__call('asort', $arr); 
	}

	/**
	 * ksort
	 *
	 * @return ArrayUtil
	 */
	public function ksort ( ) {
		$arr = func_get_args();
		return $this->__call('ksort', $arr); 
	}

	/**
	 * natcasesort
	 *
	 * @return ArrayUtil
	 */
	public function natcasesort ( ) {
		$arr = func_get_args();
		return $this->__call('natcasesort', $arr); 	
	}

	/**
	 * natsort
	 *
	 * @return ArrayUtil
	 */
	public function natsort ( ) {
		$arr = func_get_args();
		return $this->__call('natsort', $arr); 		
	} 

	/**
	 * uasort
	 *
	 * @return ArrayUtil
	 */
	public function uasort ( $cmp_function ) {
		$arr = func_get_args();
		return $this->__call('uasort', $arr);	
	}

	/**
	 * uksort
	 *
	 * @return ArrayUtil
	 */
	public function uksort ( $cmp_function ) {
		$arr = func_get_args();
		return $this->__call('uksort', $arr);	
	}
	
	/**
	 * return 값 리턴
	 * 
	 * <pre>
	 * return : $this->return 
	 * to     : $this->to()
	 * object : $this->toObject()	
	 * </pre>
	 *
	 * <code>
	 * // object 속성은 return 값을 ArrayUtil 객체로 반환해준다. 
	 * $arr = A_()->fill(10,100,1);
	 * echo $arr->object->sum()->return;   // 합계내기 
	 * </code>
	 *
	 * @return mixed 
	 */
	public function __get($key) { 

		switch (strtolower($key)) { 
			case 'return':	return $this->return;
			case 'to':		return $this->current;
			case 'source':	return $this->source;
			case 'history': return $this->history;
			case 'object':	return A_($this->return);
		}

		return '';
	}

	/**
	 * 현재 참조상태 전환
	 *
	 * <code>
	 * 
	 * // 아래의 두 코드는 동일하다. 
	 * $arr = A_()->ref()->fill(10, 100, 1);
	 * or 
	 * $arr = A_()->r(fill, 10, 100, 1);
	 *
	 * </code>
	 * 
	 * @param bool $is_ref 참조 상태, true이면 무조건 참조 적용, 기본값 true, 
	 * @return ArrayUtil 
	 * @see ArrayUtil::r()
	 */
	public function ref($is_ref = true) { 
		$this->is_ref = $is_ref;

		return $this;
	}

	/**
	 * 참조가 아닌 상태 함수를 쓸 때 자동으로 참조형태로 전환 
	 *
	 * <code>
	 *  
	 *  // 함수의 속성이 array를 리턴하는 형태이면 
	 *  // 내부 array를 바꾸고 싶다면 r 메소드를 사용해서 함수를 사용한다. 
	 *
	 *  // 내부 배열을 총 10부터 100까지 인덱스를 1로 채움 
	 *  $arr = A_()->r(fill, 10, 100, 1);
	 *  print_r($arr->to);
	 * 
	 *  // 단지 리턴값으로 쓰고 싶으면 
	 *  $arr = A_()->fill(10, 100, 1);
	 *  print_r($arr->return);
	 * 
	 * </code>
	 *
	 * @return ArrayUtil 
	 * @see ArrayUtil::ref()
	 */
	public function r() { 
		$this->ref();

		$arr = func_get_args();
		$func = array_shift($arr);

		return call_user_func_array(array($this, $func), $arr);

	}

	/** 
	 * 참조인지 리턴인지 확인 
	 *
	 * @param string $method  실행될 함수 이름 
	 * @return int    참조형태,  true : 참조, false : 리턴 
	 *
	 */
	public function hasRef($method) { 
		return $this->function_list[$method]['is_reference'];
	}

	/** 
	 * 매개변수 위치 얻기
	 *
	 * @param string $method  실행될 함수 이름 
	 * @return int    매개변수 위치 
	 *
	 */
	public function getPos($method) { 
		return $this->function_list[$method]['pos'];
	}
	
	/** 
	 * 실제 실행될 함수 
	 * 
	 * @param string $method  실행될 함수 이름 
	 * @return callback   콜백이름 
	 */
	public function getFunction($method) { 
		return $this->function_list[$method]['function'];
	}

	/**
	 * 내부 loop 실행 메소드 
	 *
	 * @param array $current 
	 * @param callback $callback
	 * @param mixed|... 기타 매개변수 리스트 
	 *
	 * @return ArrayUtil
	 */
	private function _loop() { 

		$arr = func_get_args();

		$current = array_shift($arr);
		$callback = array_shift($arr);

		$temp = array();

		foreach ($current as $key => $value) { 
			$temp[$key] = call_user_func_array($callback, array_merge(array($value), $arr));
		}

		return $temp;
	}

	private function log($method, $args, $result) { 
		$this->history[] = array('method' => $method, 'args'=> $args, 'result' => $result);
	}

	public function display($var = 'history') { 
		print_r($this->{$var});
	}	

	private function createMethodArgs($method, &$args) { 
			
		$pos = $this->getPos($method);

		$left = array_slice($args, 0, $pos);
		$right = array_slice($args, $pos);

		if ($pos == self::POS_MAX) { 
			if ($this->hasRef($method)) { 
				$args[] = &$this->current;
			} else { 
				$args[] = $this->current;
			}
		}  else { 
			if ($this->hasRef($method)) { 
				$args = array_merge($left, array(&$this->current), $right);		
			} else { 
				$args = array_merge($left, array($this->current), $right);
			}
		}
	}

	// ArrayAccess 구현 
	public function offsetExists ($index) { 
		return array_key_exists($index, $this->current);
	}

	public function offsetGet ($index) { 
		return $this->current[$index];
	}

	public function offsetSet ($index, $value) { 
		$this->current[$index] = $value;
	}

	public function offsetUnset ($index) { 
		unset($this->current[$index]);
	}

	public function append ($value) { 
		$this->current[] = $value;
	}

	// Iterator 구현 
	public function rewind() {
		$this->index = 0;
	}

	public function valid() {
		return $this->index < sizeof($this->current);
	}

	public function key() {
		return $this->index;
	}

	public function current() {
		return $this->current[$this->index];
	}

	/**
 	 * 다음 index 전진 
 	 * 
	 */
	public function next() {
		$this->index++;
	}

	/**
	 * IteratorAggregate 구현
	 * 
	 * @return ArrayUtil  
	 */ 
	public function getIterator() { 
		return $this;
	}

	/**
	 * Countable 구현
	 * 
	 * @return int 
	 */ 
	public function count($mode = 0) { 
		return count($this->current, $mode);
	}

	public function __toString() { 
		return implode(",", $this->current);
	}
}

?>